﻿//-----------------------------------------------------------------------
// Source File: "DirectoryCloner.cs"
// Create Date: 07/29/2014 10:59 PM
// Last Updated: 07/29/2014 10:59 PM
// Authors(s): nealbailey@hotmail.com
//-----------------------------------------------------------------------
// GNU GENERAL PUBLIC LICENSE
//-----------------------------------------------------------------------
// Revision: Version 3, 29 June 2007
// Copyright © 2007 Free Software Foundation, Inc. http://fsf.org/
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//  REFERENCE: http://www.gnu.org/licenses/
//-----------------------------------------------------------------------
// Copyright (c) 2010-2014 Baileysoft Solutions
//-----------------------------------------------------------------------
namespace Baileysoft.Gaming.Utils.Yasm
{
    using System;
    using System.IO;
    using System.Linq;
    using System.Threading;

    using Baileysoft.Monitor.Core.Utilities;

    #region Delegates

    /// <summary>
    /// 
    /// </summary>
    /// <param name="sender">The <see cref="object"/> triggering the event.</param>
    /// <param name="e"><see cref="FileCloneErrorEventArgs"/></param>
    public delegate void FileCloneErrorEventHandler(object sender, FileCloneErrorEventArgs e);

    /// <summary>
    /// FileCloneJobCompleted <see cref="EventHandler"/> delegate.
    /// </summary>
    /// <param name="sender">The <see cref="object"/> triggering the event.</param>
    /// <param name="e"><see cref="DirectoryCloneEventArgs"/></param>
    public delegate void FileCloneJobCompletedEventHandler(object sender, DirectoryCloneEventArgs e);

    /// <summary>
    /// FileCloneTaskCompleted <see cref="EventHandler"/> delegate.
    /// </summary>
    /// <param name="sender">The <see cref="object"/> triggering the event.</param>
    /// <param name="e"><see cref="DirectoryCloneEventArgs"/></param>
    public delegate void FileCloneTaskCompletedEventHandler(object sender, DirectoryCloneEventArgs e);

    /// <summary>
    /// FileCloneTaskStarted <see cref="EventHandler"/> delegate.
    /// </summary>
    /// <param name="sender">The <see cref="object"/> triggering the event.</param>
    /// <param name="e"><see cref="DirectoryCloneEventArgs"/></param>
    public delegate void FileCloneTaskStartedEventHandler(object sender, DirectoryCloneEventArgs e);

    #endregion Delegates

    /// <summary>
    /// <see cref="EventArgs"/> for notification when a new hash task has begun.
    /// </summary>
    public class DirectoryCloneEventArgs : EventArgs
    {
        #region Constructors

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="parameters">The parameters related to the cloning operation.</param>
        /// <param name="message">A <see cref="string"/> message related to the <see cref="IDirectoryClonable"/> operation.</param>
        public DirectoryCloneEventArgs(IDirectoryClonerArgs parameters, string message)
        {
            Parameters = parameters;
            Message = message;
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// A <see cref="string"/> message related to the <see cref="IDirectoryClonable"/> operation.
        /// </summary>
        public virtual string Message
        {
            get; set;
        }

        /// <summary>
        /// The path to the file being hashed.
        /// </summary>
        public virtual IDirectoryClonerArgs Parameters
        {
            get; set;
        }

        #endregion Properties
    }

    /// <summary>
    /// Utility class for cloning paths.
    /// </summary>
    public class DirectoryCloner : IDirectoryClonable
    {
        #region Constructors

        /// <summary>
        /// Constructor.
        /// </summary>
        public DirectoryCloner()
            : this(new FolderPair())
        {
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="cloneParameters">The <see cref="IDirectoryClonable"/> parameters object.</param>
        public DirectoryCloner(IDirectoryClonerArgs cloneParameters)
        {
            Parameters = cloneParameters;
        }

        #endregion Constructors

        #region Events

        /// <summary>
        /// Event triggered when an error occurs in the processing of the task.
        /// </summary>
        public event FileCloneErrorEventHandler FileCloneErrorOccurred;

        /// <summary>
        /// Event triggered when a file Clone job (all tasks) have completed.
        /// </summary>
        public event FileCloneJobCompletedEventHandler FileCloneJobCompleted;

        /// <summary>
        /// Event triggered when a file Clone operation completes.
        /// </summary>
        public event FileCloneTaskCompletedEventHandler FileCloneTaskCompleted;

        /// <summary>
        /// Event triggered when a file Clone operation begins.
        /// </summary>
        public event FileCloneTaskStartedEventHandler FileCloneTaskStarted;

        #endregion Events

        #region Properties

        /// <summary>
        /// Flag to cancel operation.
        /// </summary>
        public virtual bool IsTaskCancelled
        {
            get; set;
        }

        /// <summary>
        /// The <see cref="IDirectoryClonable"/> object used to define clone parameters
        /// </summary>
        public IDirectoryClonerArgs Parameters
        {
            get; set;
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Async. Clone a path to a new path and create a junction point to link the two paths.
        /// </summary>
        /// <exception cref="System.IO.IOException">The exception that is thrown when an I/O error occurs.</exception>
        /// <exception cref="System.ArgumentException">The exception that is thrown when one of the arguments provided to a method is not valid.</exception>
        /// <exception cref="System.ArgumentNullException">The exception that is thrown when a null reference (Nothing in Visual Basic) is passed to a method that does not accept it as a valid argument.</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method.</exception>
        /// <exception cref="System.UnauthorizedAccessException">The exception that is thrown when the operating system denies access because of an I/O error or a specific type of security error.</exception>
        /// <exception cref="System.IO.PathTooLongException">The exception that is thrown when a pathname or filename is longer than the system-defined maximum length.</exception>
        /// <exception cref="System.NotSupportedException">The exception that is thrown when an invoked method is not supported, or when there is an attempt to read, seek, or write to a stream that does not support the invoked functionality.</exception>
        public virtual void BeginClone()
        {
            var thread = new Thread(Clone);
            thread.Start();
        }

        /// <summary>
        /// Clone a path to a new path and create a junction point to link the two paths.
        /// </summary>
        /// <exception cref="System.IO.IOException">The exception that is thrown when an I/O error occurs.</exception>
        /// <exception cref="System.ArgumentException">The exception that is thrown when one of the arguments provided to a method is not valid.</exception>
        /// <exception cref="System.ArgumentNullException">The exception that is thrown when a null reference (Nothing in Visual Basic) is passed to a method that does not accept it as a valid argument.</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method.</exception>
        /// <exception cref="System.UnauthorizedAccessException">The exception that is thrown when the operating system denies access because of an I/O error or a specific type of security error.</exception>
        /// <exception cref="System.IO.PathTooLongException">The exception that is thrown when a pathname or filename is longer than the system-defined maximum length.</exception>
        /// <exception cref="System.NotSupportedException">The exception that is thrown when an invoked method is not supported, or when there is an attempt to read, seek, or write to a stream that does not support the invoked functionality.</exception>
        public virtual void Clone()
        {
            var isJunctioned = (!string.IsNullOrEmpty(Parameters.SourceFolder.JunctionPointTarget));
            Exception ex = null;

            if (isJunctioned)
                TryReverseClone(out ex);
            else
                TryForwardClone(out ex);

            if (ex != null)
                OnFileCloneTaskErrorOccurred(new FileCloneErrorEventArgs(ex, string.Empty));

            OnFileCloneTaskCompleted(new DirectoryCloneEventArgs(Parameters, "Cloning operation completed."));
        }

        /// <summary>
        /// Determines if a directory is empty or not
        /// <para>True if empty, False otherwise</para>
        /// </summary>
        /// <param name="path">The directory path.</param>
        /// <returns><see cref="bool"/></returns>
        /// <exception cref="System.ArgumentException">The exception that is thrown when one of the arguments provided to a method is not valid.</exception>
        /// <exception cref="System.ArgumentNullException">The exception that is thrown when a null reference (Nothing in Visual Basic) is passed to a method that does not accept it as a valid argument.</exception>
        /// <exception cref="System.IO.DirectoryNotFoundException">The exception that is thrown when part of a file or directory cannot be found.</exception>
        /// <exception cref="System.IO.IOException">The exception that is thrown when an I/O error occurs.</exception>
        /// <exception cref="System.IO.PathTooLongException">The exception that is thrown when a pathname or filename is longer than the system-defined maximum length.</exception>
        /// <exception cref="System.Security.SecurityException">The exception that is thrown when a security error is detected.</exception>
        /// <exception cref="System.UnauthorizedAccessException">The exception that is thrown when the operating system denies access because of an I/O error or a specific type of security error.</exception>
        protected internal virtual bool IsDirectoryEmpty(string path)
        {
            return !Directory.EnumerateFileSystemEntries(path).Any();
        }

        /// <summary>
        /// Raise the <see cref="FileCloneJobCompleted"/> event.
        /// </summary>
        /// <param name="e"><see cref="DirectoryCloneEventArgs"/></param>
        protected internal void OnFileCloneJobCompleted(DirectoryCloneEventArgs e)
        {
            if (FileCloneJobCompleted != null) // prevents double triggering
                FileCloneJobCompleted(this, e);
        }

        /// <summary>
        /// Raise the <see cref="FileCloneTaskCompleted"/> event.
        /// </summary>
        /// <param name="e"><see cref="DirectoryCloneEventArgs"/></param>
        protected internal void OnFileCloneTaskCompleted(DirectoryCloneEventArgs e)
        {
            if (FileCloneTaskCompleted != null) // prevents double triggering
                FileCloneTaskCompleted(this, e);
        }

        /// <summary>
        /// Raise the <see cref="FileCloneErrorOccurred"/> event.
        /// </summary>
        /// <param name="e"><see cref="FileCloneErrorEventArgs"/></param>
        protected internal void OnFileCloneTaskErrorOccurred(FileCloneErrorEventArgs e)
        {
            if (FileCloneErrorOccurred != null) // prevents double triggering
                FileCloneErrorOccurred(this, e);
        }

        /// <summary>
        /// Raise the <see cref="FileCloneTaskStarted"/> event.
        /// </summary>
        /// <param name="e"><see cref="DirectoryCloneEventArgs"/></param>
        protected internal void OnFileCloneTaskStarted(DirectoryCloneEventArgs e)
        {
            if (FileCloneTaskStarted != null) // prevents double triggering
                FileCloneTaskStarted(this, e);
        }

        /// <summary>
        /// Clone the SourceFolder to the DestinationFolder.
        /// </summary>
        /// <remarks>
        /// This operation clones all the files from one folder into another folder. 
        /// If the clone succeeds, then the original path is deleted and replaced 
        /// with a junction point / sym-link to the cloned path.
        /// </remarks>
        /// <exception cref="System.IO.IOException">The exception that is thrown when an I/O error occurs.</exception>
        /// <exception cref="System.ArgumentException">The exception that is thrown when one of the arguments provided to a method is not valid.</exception>
        /// <exception cref="System.ArgumentNullException">The exception that is thrown when a null reference (Nothing in Visual Basic) is passed to a method that does not accept it as a valid argument.</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method.</exception>
        /// <exception cref="System.UnauthorizedAccessException">The exception that is thrown when the operating system denies access because of an I/O error or a specific type of security error.</exception>
        /// <exception cref="System.IO.PathTooLongException">The exception that is thrown when a pathname or filename is longer than the system-defined maximum length.</exception>
        /// <exception cref="System.NotSupportedException">The exception that is thrown when an invoked method is not supported, or when there is an attempt to read, seek, or write to a stream that does not support the invoked functionality.</exception>
        protected virtual void ForwardClone()
        {
            var message = string.Empty;

            if (JunctionPoint.Exists(Parameters.DestinationFolder))
                JunctionPoint.Delete(Parameters.DestinationFolder);

            //Now Create all of the directories
            foreach (var dirPath in Directory.GetDirectories(
                Parameters.SourceFolder.Path, "*", SearchOption.AllDirectories))
            {
                if (IsTaskCancelled)
                    return;

                Directory.CreateDirectory(dirPath.Replace(Parameters.SourceFolder.Path, Parameters.DestinationFolder));
            }

            //Copy all the files & Replaces any files with the same name
            foreach (var sourceFile in Directory.GetFiles(Parameters.SourceFolder.Path, "*", SearchOption.AllDirectories))
            {
                if (IsTaskCancelled)
                    return;

                var shortFileName = Path.GetFileName(sourceFile);
                var copyToFullName = sourceFile.Replace(Parameters.SourceFolder.Path,
                                                        Parameters.DestinationFolder);

                message = string.Format("Copy: {0} -> {1}", shortFileName, copyToFullName);
                OnFileCloneTaskStarted(new DirectoryCloneEventArgs(Parameters, message));
                File.Copy(sourceFile, copyToFullName, true);
            }

            if (IsTaskCancelled)
                return;

            // Delete all the files the have been cloned
            message = string.Format("Delete: {0}", Parameters.SourceFolder.Path);
            OnFileCloneTaskStarted(new DirectoryCloneEventArgs(Parameters, message));
            Directory.Delete(Parameters.SourceFolder.Path, true);

            if (IsTaskCancelled)
                return;

            // Create a new junction point that links the original folder to the cloned files
            message = string.Format("Junction: {0}-> {1}", Parameters.SourceFolder.Path, Parameters.DestinationFolder);
            OnFileCloneTaskStarted(new DirectoryCloneEventArgs(Parameters, message));

            if (!JunctionPoint.Exists(Parameters.SourceFolder.Path))
                JunctionPoint.Create(Parameters.SourceFolder.Path, Parameters.DestinationFolder, true);
        }

        /// <summary>
        /// Clone the DestinationFolder to the SourceFolder.
        /// </summary>
        /// <remarks>
        /// This operation clones all the files from one folder into another folder. 
        /// If the clone succeeds, then the original path is deleted and replaced 
        /// with a junction point / sym-link to the cloned path.
        /// </remarks>
        /// <exception cref="System.IO.IOException">The exception that is thrown when an I/O error occurs.</exception>
        /// <exception cref="System.ArgumentException">The exception that is thrown when one of the arguments provided to a method is not valid.</exception>
        /// <exception cref="System.ArgumentNullException">The exception that is thrown when a null reference (Nothing in Visual Basic) is passed to a method that does not accept it as a valid argument.</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method.</exception>
        /// <exception cref="System.UnauthorizedAccessException">The exception that is thrown when the operating system denies access because of an I/O error or a specific type of security error.</exception>
        /// <exception cref="System.IO.PathTooLongException">The exception that is thrown when a pathname or filename is longer than the system-defined maximum length.</exception>
        /// <exception cref="System.NotSupportedException">The exception that is thrown when an invoked method is not supported, or when there is an attempt to read, seek, or write to a stream that does not support the invoked functionality.</exception>
        protected virtual void ReverseClone()
        {
            var message = string.Empty;

            if (JunctionPoint.Exists(Parameters.SourceFolder.Path))
                JunctionPoint.Delete(Parameters.SourceFolder.Path);

            // Create all of the directories in the destination folder
            foreach (var dirPath in Directory.GetDirectories(Parameters.DestinationFolder, "*", SearchOption.AllDirectories))
            {
                if (IsTaskCancelled)
                    return;

                Directory.CreateDirectory(dirPath.Replace(Parameters.DestinationFolder, Parameters.SourceFolder.Path));
            }

            // Copy all the files & Replaces any files with the same name
            foreach (var sourceFile in Directory.GetFiles(Parameters.DestinationFolder, "*", SearchOption.AllDirectories))
            {
                if (IsTaskCancelled)
                    return;

                var shortFileName = Path.GetFileName(sourceFile);
                var copyToFullName = sourceFile.Replace(Parameters.DestinationFolder,
                                                        Parameters.SourceFolder.Path);

                message = string.Format("Copy: {0} -> {1}", shortFileName, copyToFullName);
                OnFileCloneTaskStarted(new DirectoryCloneEventArgs(Parameters, message));

                File.Copy(sourceFile, copyToFullName, true);
            }

            if (IsTaskCancelled)
                return;

            // Delete all the files the have been cloned
            message = string.Format("Delete: {0}", Parameters.DestinationFolder);
            OnFileCloneTaskStarted(new DirectoryCloneEventArgs(Parameters, message));
            Directory.Delete(Parameters.DestinationFolder, true);

            if (IsTaskCancelled)
                return;

            // Create a new junction point that links the original folder to the cloned files
            message = string.Format("Junction: {0} -> {1}", Parameters.DestinationFolder, Parameters.SourceFolder.Path);
            OnFileCloneTaskStarted(new DirectoryCloneEventArgs(Parameters, message));

            if (!JunctionPoint.Exists(Parameters.DestinationFolder))
                JunctionPoint.Create(Parameters.DestinationFolder, Parameters.SourceFolder.Path, true);
        }

        /// <summary>
        /// Try to clone the SourceFolder to the DestinationFolder.
        /// </summary>
        /// <param name="exception">The first occurring <see cref="Exception"/>.</param>
        /// <returns>A <see cref="bool"/> flag indicating the success or failure of the operation.</returns>
        protected virtual bool TryForwardClone(out Exception exception)
        {
            var result = false;
            exception = null;

            try
            {
                ForwardClone();
                result = true;
            }
            catch (Exception e)
            {
                exception = e;
            }
            return result;
        }

        /// <summary>
        /// Try to clone the DestinationFolder to the SourceFolder.
        /// </summary>
        /// <param name="exception">The first occurring <see cref="Exception"/>.</param>
        /// <returns>A <see cref="bool"/> flag indicating the success or failure of the operation.</returns>
        protected virtual bool TryReverseClone(out Exception exception)
        {
            var result = false;
            exception = null;

            try
            {
                ReverseClone();
                result = true;
            }
            catch (Exception e)
            {
                exception = e;
            }
            return result;
        }

        #endregion Methods
    }

    /// <summary>
    /// <see cref="EventArgs"/> for notification when a new hash task has begun.
    /// </summary>
    public class FileCloneErrorEventArgs : EventArgs
    {
        #region Constructors

        /// <summary>
        /// 
        /// </summary>
        /// <param name="exception">The <see cref="Exception"/> parameters</param>
        /// <param name="message">A <see cref="string"/> message related to the <see cref="IDirectoryClonable"/> operation.</param>
        public FileCloneErrorEventArgs(Exception exception, string message)
        {
            Exception = exception;
            Message = message;
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// The exception that has occurred.
        /// </summary>
        public virtual Exception Exception
        {
            get; set;
        }

        /// <summary>
        /// A <see cref="string"/> message related to the <see cref="IDirectoryClonable"/> operation.
        /// </summary>
        public virtual string Message
        {
            get; set;
        }

        #endregion Properties
    }
}